package com.pubinfo.passbook.engine;

import com.pubinfo.passbook.common.entity.TEngineComputeChain;
import com.pubinfo.passbook.common.exception.ServiceException;
import com.pubinfo.passbook.common.model.dto.witeoff.MedicineOnJobOrRetireDTO;
import com.pubinfo.passbook.common.service.engine.EngineComputeChainService;
import com.pubinfo.passbook.engine.chain.ComputeChain;
import com.pubinfo.passbook.engine.chain.medicine.ComputeMedicineChain;
import com.pubinfo.passbook.engine.constants.ComputeChainType;
import com.pubinfo.passbook.engine.factory.ComputeChainFactory;
import com.pubinfo.passbook.engine.result.MedicineComputeResult;
import com.pubinfo.passbook.common.utils.ApplicationContextUilts;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;

/**
 * 规则计算引擎入口
 *
 * @author zhouch
 * @date 2021/12/13 14:02
 */
@Slf4j
@Component
public class RuleEngineEntranceDefault implements RuleEngineEntrance {

    @Resource
    EngineComputeChainService chainService;

    @Resource
    ApplicationContext context;

    @Resource
    ComputeChainFactory chainFactory;


    @PostConstruct
    void initEngineApplicationContext() {
        log.info("规则引擎环境初始化...");
        ApplicationContextUilts.setContext(context);
    }

    /**
     * 执行通用计算链
     *
     * @param initVal
     * @return
     */
    public BigDecimal executeCommonComputeChainByChainId(Integer chainId, BigDecimal initVal) {
        ComputeChain<Object, BigDecimal> computor = (ComputeChain<Object, BigDecimal>) chainFactory.getComputor(chainId);

        if (Boolean.FALSE == StringUtils.equals(computor.getChainType(), ComputeChainType.COMMON)) {
            throw new ServiceException("该计算链不是一个通用计算链：ID = " + chainId);
        }

        // 通用计算链不依赖外部任何数据，所以data = null
        BigDecimal result = computor.compute(null, initVal);
        computor.down();
        return result;
    }

    public BigDecimal executeCommonComputeChain(TEngineComputeChain chainInfo, BigDecimal initVal) {
        ComputeChain<Object, BigDecimal> computor = (ComputeChain<Object, BigDecimal>) chainFactory.getComputor(chainInfo);

        if (Boolean.FALSE == StringUtils.equals(computor.getChainType(), ComputeChainType.COMMON)) {
            throw new ServiceException("该计算链不是一个通用计算链");
        }

        // 通用计算链不依赖外部任何数据，所以data = null
        BigDecimal result = computor.compute(null, initVal);
        computor.down();
        return result;
    }

    /**
     * 医药费计算入口
     *
     * @param data    医药费表单数据
     * @param chainId 规则ID
     * @return
     */
    @Override
    public MedicineComputeResult executeMedicineComputeChain(final MedicineOnJobOrRetireDTO data, Integer chainId) {
        BigDecimal initVal = new BigDecimal(0);
        return executeMedicineComputeChain(data, chainId, initVal);
    }

    /**
     * 医药费计算入口
     *
     * @param data    医药费表单数据
     * @param chainId 规则ID
     * @param initVal 初始值
     * @return
     */
    @Override
    @Transactional
    public MedicineComputeResult executeMedicineComputeChain(
            @NonNull final MedicineOnJobOrRetireDTO data,
            @NonNull Integer chainId,
            @NonNull BigDecimal initVal) {
        log.info(StringUtils.EMPTY);
        log.info("===================================== 引擎医药费计算开始 =====================================");
        log.info(StringUtils.EMPTY);

        MedicineComputeResult result = new MedicineComputeResult();

        log.info("正在执行医药费计算链...");

        ComputeMedicineChain chain =
                (ComputeMedicineChain) chainFactory.getComputor(chainId);

        if (!StringUtils.equals(chain.getChainType(), ComputeChainType.MEDICINE)) {
            throw new ServiceException(
                    "错误的计算链绑定: 该计算输入的数据类型为:" + ComputeChainType.MEDICINE + " 但是计算链的类型为:" + chain.getChainType());
        }

        log.info("初始化完成, 开始执行计算链...");

        BigDecimal computeResult = chain.compute(data, initVal);

        // 设置其他的计算信息
        result.setResult(computeResult);
        result.setMessage(chain.getChainExecuteMessage());
        result.setNeedAudit(chain.isNeedAudit());

        log.info(StringUtils.EMPTY);
        log.info("===================================== 引擎医药费计算结束 =====================================");
        log.info(StringUtils.EMPTY);

        return result;
    }


    /**
     * @param chainInfo
     * @return
     */
    public boolean checkComputeChain(TEngineComputeChain chainInfo) {
        log.info("检查计算链可用性: {}, 开始初始化计算链...", chainInfo.getName());

        /**
         * 初始化计算链就包含了链内部所有单元, 参数, 子链, 所有的计算类型检查.
         */
        ComputeChain<?, ?> chain = chainFactory.getComputor(chainInfo);

        log.info("{}计算链可用性检查完毕, 数据：{}", chainInfo.getName(), chainInfo);

        return chain != null;
    }

}
